---
title: 关于钩子
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import CodeBlock from "@theme/CodeBlock";
import pubspec from "./getting_started/pubspec";
import dartHelloWorld from "./getting_started/dart_hello_world";
import helloWorld from "./getting_started/hello_world";
import dartPubspec from "./getting_started/dart_pubspec";
import {
  trimSnippet,
  AutoSnippet,
  When,
} from "../../../../src/components/CodeSnippet";

本页解释了什么是钩子，以及它们如何与Riverpod相关。

“Hooks”是独立于Riverpod: [flutter_hooks] 的package中的常用工具。  
尽管 [flutter_hooks] 是一个完全独立的package，与Riverpod没有任何关系(至少没有直接关系)，
但将Riverpod和 [flutter_hooks] 放在一起很常见。
毕竟，Riverpod和 [flutter_hooks] 是由同一个团队维护的。

钩子是完全可选的。特别是如果你刚开始使用Flutter，那么你不必使用钩子。
虽然它们是很强大的工具，但没有Flutter的那个“味”。
所以，入门选择使用Flutter和Riverpod这样的搭配。当你有更多相关的经验时，那么你可以回来尝试一下钩子。

## 什么是钩子？

钩子是在widget中使用的函数。它们被设计为 [StatefulWidget] 的替代品，以使逻辑更具可重用性和可组合性。

Hooks是一个来自 [React](https://reactjs.org/) 的概念，
而 [flutter_hooks] 仅仅是Flutter的一个React实现。
所以是的，在Flutter中钩子可能感觉有点不合适。
理想情况下，将来我们会有专门为Flutter设计的用钩子解决问题的解决方案。

如果说Riverpod的provider是针对“全局”应用状态的，钩子则是针对本地widget的状态。
钩子通常用于处理有状态的UI对象，如 [TextEditingController](https://api.flutter.dev/flutter/widgets/TextEditingController-class.html),
[AnimationController](https://api.flutter.dev/flutter/animation/AnimationController-class.html)。  
它们也可以作为“构建器(builder)”模式的替代品，
用不涉及“嵌套”的替代品来替换 [FutureBuilder](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html)/[TweenAnimatedBuilder](https://api.flutter.dev/flutter/widgets/TweenAnimationBuilder-class.html) 之类的widget——极大地提高了可读性。

一般来说，钩子有助于：

- 表单
- 动画
- 响应用户事件
- ……

例如，我们可以使用钩子手动实现淡入动画，其中widget开始时不可见，然后慢慢出现。

如果我们使用 [StatefulWidget],，代码看起来会像这样：

```dart
class FadeIn extends StatefulWidget {
  const FadeIn({Key? key, required this.child}) : super(key: key);

  final Widget child;

  @override
  State<FadeIn> createState() => _FadeInState();
}

class _FadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
  late final AnimationController animationController = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 2),
  );

  @override
  void initState() {
    super.initState();
    animationController.forward();
  }

  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animationController,
      builder: (context, child) {
        return Opacity(
          opacity: animationController.value,
          child: widget.child,
        );
      },
    );
  }
}
```

使用钩子会是：

```dart
class FadeIn extends HookWidget {
  const FadeIn({Key? key, required this.child}) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context) {
    // Create an AnimationController. The controller will automatically be
    // disposed when the widget is unmounted.
    final animationController = useAnimationController(
      duration: const Duration(seconds: 2),
    );

    // useEffect is the equivalent of initState + didUpdateWidget + dispose.
    // The callback passed to useEffect is executed the first time the hook is
    // invoked, and then whenever the list passed as second parameter changes.
    // Since we pass an empty const list here, that's strictly equivalent to `initState`.
    useEffect(() {
      // start the animation when the widget is first rendered.
      animationController.forward();
      // We could optionally return some "dispose" logic here
      return null;
    }, const []);

    // Tell Flutter to rebuild this widget when the animation updates.
    // This is equivalent to AnimatedBuilder
    useAnimation(animationController);

    return Opacity(
      opacity: animationController.value,
      child: child,
    );
  }
}
```

在这段代码中有一些有趣的事情需要注意：

- 不存在内存泄漏。这段代码不会在widget重新构建时重新创建一个新的 `AnimationController`，
  并且它在widget销毁时正确地释放控制器。

- 在同一个小部件中，可以任意多次地使用钩子。
  因此，如果我们想要我们可以创建多个 `AnimationController`：

  ```dart
  @override
  Widget build(BuildContext context) {
    final animationController = useAnimationController(
      duration: const Duration(seconds: 2),
    );
    final anotherController = useAnimationController(
      duration: const Duration(seconds: 2),
    );

    ...
  }
  ```

  这创建了两个控制器，但没有任何负面效果。

- 如果我们愿意，我们可以将这个逻辑重构为一个单独的可重用函数：

  ```dart
  double useFadeIn() {
    final animationController = useAnimationController(
      duration: const Duration(seconds: 2),
    );
    useEffect(() {
      animationController.forward();
      return null;
    }, const []);
    useAnimation(animationController);
    return animationController.value;
  }
  ```

  然后我们可以在widget中使用这个函数，只要那个widget是[HookWidget]：

  ```dart
  class FadeIn extends HookWidget {
    const FadeIn({Key? key, required this.child}) : super(key: key);

    final Widget child;

    @override
    Widget build(BuildContext context) {
      final fade = useFadeIn();

      return Opacity(opacity: fade, child: child);
    }
  }
  ```

  注意我们的 `useFadeIn` 函数是如何完全独立于我们的 `FadeIn` widget的。  
  如果我们愿意，我们可以在一个完全不同的widget中使用 `useFadeIn` 函数，它仍然也可以工作!

## 如何使用钩子

钩子有独特的约束条件：

- 它们只能在扩展了 [HookWidget] 的widget的 `build` 方法中使用：

  **正确**:

  ```dart
  class Example extends HookWidget {
    @override
    Widget build(BuildContext context) {
      final controller = useAnimationController();
      ...
    }
  }
  ```

  **错误**:

  ```dart
  // 不是一个HookWidget
  class Example extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      final controller = useAnimationController();
      ...
    }
  }
  ```

  **错误**:

  ```dart
  class Example extends HookWidget {
    @override
    Widget build(BuildContext context) {
      return ElevatedButton(
        onPressed: () {
          // Not _actually_ inside the "build" method, but instead inside
          // a user interaction lifecycle (here "on pressed").
          final controller = useAnimationController();
        },
        child: Text('click me'),
      );
    }
  }
  ```

- 它们不能在条件语句或循环语句内使用。

  **错误**:

  ```dart
  class Example extends HookWidget {
    const Example({required this.condition, super.key});
    final bool condition;
    @override
    Widget build(BuildContext context) {
      if (condition) {
        // Hooks should not be used inside "if"s/"for"s, ...
        final controller = useAnimationController();
      }
      ...
    }
  }
  ```

有关钩子的更多信息，请参见 [flutter_hooks]。

[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html
[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html
[riverpod]: https://github.com/rrousselgit/riverpod
[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod
[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod
[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks
